#pragma once

namespace juce::dsp {
template <typename FloatType>
class EnvelopeFollower {
public:
    EnvelopeFollower()
        : envelope(0)
        , attack(0)
        , release(0)
    {
    }

    void setup(FloatType attackMs, FloatType releaseMs, FloatType sampleRate)
    {
        attack = std::pow(0.01, 1.0f / (attackMs * sampleRate * 0.01));
        release = std::pow(0.01, 1.0f / (releaseMs * sampleRate * 0.01));
    }

    void process(const FloatType sample)
    {
        FloatType v = std::abs(sample);
        if (envelope > 0)
            envelope = attack * (envelope - v) + v;
        else
            envelope = release * (envelope - v) + v;
    }

    FloatType getSample() { return envelope; }

private:
    FloatType envelope, attack, release;
};

template <typename FloatType>
class WaveShaperParametric {
    using EnvFollower = EnvelopeFollower<FloatType>;

public:
    WaveShaperParametric() noexcept = default;
    ~WaveShaperParametric() noexcept
    {
        if (!envFollowers.isEmpty()) {
            envFollowers.clear();
        }
    }

    //==============================================================================
    /** Applies a new gain as a linear value. */
    void setGainLinear(float gain) noexcept { this->gain = gain; }

    /** Returns the current gain as a linear value. */
    float getGainLinear() const noexcept { return gain; }

    //==============================================================================
    /** Called before processing starts. */
    void prepare(const ProcessSpec& spec)
    {
        if (envFollowers.isEmpty()) {
            for (int i = 0; i < spec.numChannels; i++) {
                envFollowers.add(EnvFollower());
                envFollowers[i].setup(5, 5, spec.sampleRate);
            }
        }
    }

    /** Resets the internal state of the wave shaper */
    void reset() noexcept { gain = 0; }

    FloatType processSample(FloatType x, FloatType parameter)
    {
        x *= gain;
        return (x * (std::abs(x) + parameter) / (x * x + (parameter - 1) * std::abs(x) + 1)) * 0.7;
    }

    /** Processes the input and output buffers supplied in the processing context.
   */
    template <typename ProcessContext>
    void process(const ProcessContext& context) noexcept
    {
        auto&& inBlock = context.getInputBlock();
        auto&& outBlock = context.getOutputBlock();

        jassert(inBlock.getNumChannels() == outBlock.getNumChannels());
        jassert(inBlock.getNumSamples() == outBlock.getNumSamples());

        auto len = inBlock.getNumSamples();
        auto numChannels = inBlock.getNumChannels();

        if (context.isBypassed) {
            if (context.usesSeparateInputAndOutputBlocks())
                outBlock.copyFrom(inBlock);

            return;
        }

        for (size_t chan = 0; chan < numChannels; ++chan) {
            auto* src = inBlock.getChannelPointer(chan);
            auto* dst = outBlock.getChannelPointer(chan);
            FloatType sample;

            for (size_t i = 0; i < len; ++i) {
                sample = src[i];
                envFollowers[i].process(sample);
                auto parameter = envFollowers[i].getSample();
                parameter = jlimit<FloatType>(5, 100, parameter * 30.f + 5.f);
                dst[i] = processSample(sample, parameter);
            }
        }
    }

private:
    //==============================================================================
    FloatType gain = 0;
    juce::Array<EnvFollower> envFollowers = juce::Array<EnvFollower>();
};

} // namespace juce::dsp
